function BsfMetric() {
    this.project="";//项目名
    this.userId="";//userId
    this.uuId="";//uuid
    this.app="";//app版本
    this.spa=false;//是否是单页面模式
    this.other={};//其他信息记录
    this.apiWebSite="http://localhost:8081";//api网站地址
    this.domChangeMonitorEnabled=true;//检测dom变化
    this.domChangeRefreshTime=1000;//dom更新配置刷新时间
    this.printDebugLog=true;//打印debug日志
    this.pageStartTime=null;
    this.tool=new BsfMetricTool();
    this.ip={};
    this.config=[];
    this.currentPage={};//当前页面
    this.init=function (){
        this.currentPage=this.tool.getPage();
        this.tool.domError((event,error)=>{
            let e=this.createErrorEvent(event,error);
            this.tool.httpJson("POST",this.apiWebSite+"/api/metric_log/",e, ()=>{
                this.debugLog("发送错误成功!");
            });
        });
        this.loadIp();
        this.downloadConfigScript((r)=>{
            this.config=r;
            this.loadConfig();
        });
        this.tool.domReady(()=>{
            this.pageLoad();
        });
        this.tool.domUnload(()=>{
            this.pageEnd();
        });
        //单页面模式
        if(this.spa){
            this.tool.spaChange(()=>{
                this.pageEnd();
                this.pageLoad();
            });
        }
        //配置刷新监听
        if(this.domChangeMonitorEnabled){
            this.tool.domChange(this.domChangeRefreshTime,()=>{
                this.loadConfig();
                this.debugLog("dom发生变化"+this.domChangeRefreshTime+"ms配置已刷新!")
            });
        }
    }
    //页面加载
    this.pageLoad=function (){
        this.currentPage=this.tool.getPage();
        this.pageStartTime=new Date();
        let e= this.createPageEvent("pageLoad",this.pageStartTime,null);
        this.tool.httpJson("POST",this.apiWebSite+"/api/metric_page/",e, ()=>{
            this.debugLog("打开页面:"+e.url);
        });
        this.loadConfig();
    }
    //页面结束
    this.pageEnd=function (){
        let e= this.createPageEvent("pageEnd",this.pageStartTime,new Date());
        this.pageStartTime=null;
        //this.tool.httpJson("POST",this.apiWebSite+"/api/metric/",e,null);
        let blob = new Blob([JSON.stringify(e)], {type : 'application/json'});
        navigator.sendBeacon(this.apiWebSite+"/api/metric_page/", blob);
        this.debugLog("卸载页面:"+e.url);
    }
    //加载动态事件绑定配置
    this.loadConfig=function (){
        for(let i=0;i<this.config.length;i++){
            let c=this.config[i]
            let regex = new RegExp(c.urlRegex);
            if(regex.test(this.currentPage.pathname)) {
                for(let j=0;j<c.binds.length;j++) {
                    let b=c.binds[j];
                    let selectors = document.querySelectorAll(b.selector);
                    if (!this.tool.isNull(selectors)) {
                        for(let k=0;k<selectors.length;k++){
                            let selector=selectors[k];
                            this.bindEvent(c,selector,b.event);
                        }
                    }
                }
            }
        }
        //预览模式
        if(this.currentPage.href.indexOf("BsfPreview")>-1){
            sessionStorage.setItem("BsfPreview","1");
        }
        if(!this.tool.isNull(sessionStorage.getItem("BsfPreview"))) {
            if(this.tool.isNull(document.getElementById('bsfPreviewId'))) {
                document.body.innerHTML+="<div id='bsfPreviewId' style='position: absolute;top:10px;left:10px;'><button onclick='javascript:sessionStorage.removeItem(\"BsfPreview\");location.reload();'>退出预览模式</button></div>";
            }
        }
    }
    //绑定事件
    this.bindEvent=function (config,element,event){
        let attr = "bsfEventBind_"+event;
        if(!element.hasAttribute(attr)) {
            this.debugLog(["绑定事件",event,element]);
            element.addEventListener(event,  (e)=> {
                let e2= this.createActionEvent(event,e);
                this.tool.httpJson("POST",this.apiWebSite+"/api/metric_event/",e2, ()=>{
                    this.debugLog(["事件触发成功",event,config]);
                });
            });
            //染色预览
            if(!this.tool.isNull(sessionStorage.getItem("BsfPreview"))) {
                element.style.backgroundColor = "yellow";
                element.setAttribute("title",JSON.stringify(config));
            }
            element.setAttribute(attr, "true");
        }
    }


    //配置script加载
    this.downloadConfigScript=function (loadEvent){
        let config = sessionStorage.getItem("bsfMetricConfig");
        if(this.tool.isNull(config)||this.apiWebSite.indexOf("localhost")>-1) {
            let url = this.apiWebSite + "/api/metric_config/?project=" + this.project;
            this.tool.httpJson("GET", url, {}, function (r) {
                sessionStorage.setItem("bsfMetricConfig",JSON.stringify(r));
                loadEvent && loadEvent(r);
            });
        }else{
            loadEvent && loadEvent(JSON.parse(config));
        }
    }

    //获取ip
    this.loadIp=function (){
        let ipJson=sessionStorage.getItem("ip");
        if(this.tool.isNull(ipJson)) {
            let script = document.createElement("script");
            script.src="http://pv.sohu.com/cityjson?ie=utf-8";
            document.body.appendChild(script);
            script.onload = ()=>{
                this.ip.ip=returnCitySN["cip"];
                this.ip.ip_id=returnCitySN["cid"];
                this.ip.ip_address=returnCitySN["cname"];
                sessionStorage.setItem("ip",JSON.stringify(this.ip));
                this.ip = JSON.parse(sessionStorage.getItem("ip"));
            };
        }else{
            this.ip = JSON.parse(ipJson);
        }
    }

    this.createErrorEvent=function (event,error){
        let log="event:{event},ip:{ip},browse:{browse},os:{os},app:{app},referrer:{referrer},screen:{screen}\r\n"
            .replace("{event}",event)
            .replace("{ip}",JSON.stringify(this.ip)) .replace("{browse}",this.tool.getBrowse()) .replace("{os}",this.tool.getOs())
            .replace("{app}",this.app) .replace("{referrer}",document.referrer) .replace("{screen}",screen.width+"X"+screen.height);
        log=log+error;
        return {
            project:this.project,
            createTime: this.tool.formatDate(new Date()), // 创建时间
            type:"错误",
            log:log,//错误信息
            url: this.currentPage.pathname+this.currentPage.search, // 页面地址
            userId: this.userId, // 用户标识
            uuid: this.uuId, // 浏览器唯一标识
            other:this.other
        }
    }

    //动作事件
    this.createActionEvent=function (event,mouseEvent){
        return {
            project:this.project,
            createTime: this.tool.formatDate(new Date()), // 创建时间
            event: event, // 事件类型
            pageX:mouseEvent.pageX,
            pageY:mouseEvent.pageY,
            ip: this.ip, //ip地址,后端动态替换
            browse:this.tool.getBrowse(),//浏览器
            os: this.tool.getOs(), // 操作系统
            app:this.app,//app版本
            referrer: document.referrer, // 来源地址
            screen: screen.width+"X"+screen.height, // 屏幕分辨率
            url: this.currentPage.pathname+this.currentPage.search, // 页面地址
            userId: this.userId, // 用户标识
            uuid: this.uuId, // 浏览器唯一标识
            other:this.other
        }
    }
    //页面事件
    this.createPageEvent=function (event,createTime,endTime){
        return {
            project:this.project,
            createTime: this.tool.formatDate(createTime), // 创建时间
            endTime:this.tool.formatDate(endTime), //结束时间
            event: event, // 事件类型
            ip: this.ip, //ip地址,后端动态替换
            browse:this.tool.getBrowse(),//浏览器
            os: this.tool.getOs(), // 操作系统
            app:this.app,//app版本
            referrer: document.referrer, // 来源地址
            screen: screen.width+"X"+screen.height, // 屏幕分辨率
            url: this.currentPage.pathname+this.currentPage.search, // 页面地址
            userId: this.userId, // 用户标识
            uuid: this.uuId, // 浏览器唯一标识
            other:this.other
        }
    }
    //debug日志
    this.debugLog=function (msg){
        if(this.printDebugLog) {
            console.log(msg);
        }
    }
}


function BsfMetricTool(){
    //spaChange
    this.spaChange=function (spaFun){
        window.addEventListener('hashchange', function() {
            spaFun&&spaFun()
        }, true);
        // 第一阶段：我们对原生方法进行包装，调用前执行 dispatchEvent 了一个同样的事件
        function aop (type) {
            let source = window.history[type];
            return function () {
                let event = new Event(type);
                event.arguments = arguments;
                window.dispatchEvent(event);
                let rewrite = source.apply(this, arguments);
                return rewrite;
            };
        }
        // 第二阶段：将 pushState 和 replaceState 进行基于 AOP 思想的代码注入
        window.history.pushState = aop('pushState');
        window.history.replaceState = aop('replaceState'); // 更改路由，不会留下历史记录
        // 第三阶段：捕获pushState 和 replaceState
        window.addEventListener('pushState', function() {
            spaFun&&spaFun()
        }, true)
        window.addEventListener('replaceState', function() {
            spaFun&&spaFun()
        }, true)
    }
    //dom加载完成,后续兼容多页面切换
    this.domReady= function (fn){
        if(document.addEventListener){
            document.addEventListener('DOMContentLoaded',function(){
                fn&&fn();//处理事情
            },false);
        }else{
            /*监控资源情况，ie8及以下不支持addEventListener*/
            document.onreadystatechange=function(){
                /*dom加载完成的时候*/
                if(document.readyState=='complete'){
                    fn&&fn();//处理事情
                }
            };
        }
    }
    //dom卸载事件,兼容多页面切换
    this.domUnload=function (fn){
        window.addEventListener('unload', function(event) {
            fn&&fn();
        });
    }
    //error事件
    this.domError=function (fn){
        window.onerror =  (msg, url, lineno, colno, stack) => {
            let error="地址:{url}\r\n行:{lineno}\r\n列:{colno}\r\n错误:{msg}\r\n堆栈:{stack}\r\n"
                .replace("{url}",url).replace("{lineno}",lineno+"").replace("{colno}",colno+"")
                .replace("{msg}",msg).replace("{stack}",this.isNull(stack)?"":stack.stack);
            fn&&fn("onerror",error);
        }
        window.addEventListener('unhandledrejection', (e) => {
            let error="错误:{msg}\r\n堆栈:{stack}\r\n"
                .replace("{msg}",e.reason.message).replace("{stack}",e.reason.stack);
            fn&&fn("unhandledrejection",error);
        });
        window.addEventListener('error', (e) => {
            let target = e.target;
            if (target.localName) {
                let error="地址:{url}\r\n类型:{type}\r\n"
                    .replace("{url}",target.src || target.href).replace("{type}",target.localName);
                fn&&fn("error",error);
            }
        }, true)
    }
    //获取页面信息
    this.getPage=function (){
        return {
            referrer: document.referrer,
            host: window.location.host,
            hostname: window.location.hostname,
            href: window.location.href,
            origin: window.location.origin,
            pathname: window.location.pathname,
            search:  window.location.search
        }
    }
    //post 数据
    this.httpJson=function (method,url,sendJson,fun){
        let httpRequest = new XMLHttpRequest();//第一步：建立所需的对象
        httpRequest.open(method, url, true);  //调用AddDataToServer
        httpRequest.setRequestHeader("Content-Type", "application/json");   //设置请求头信息
        httpRequest.onreadystatechange = function () {
            if (httpRequest.readyState == 4) {
                //httpRequest.
                if (httpRequest.status == 200) {
                    let r = httpRequest.responseText;
                    if (fun != null && typeof fun != "undefined") {
                        fun(JSON.parse(r));
                    }
                }
                else{
                    console.error([url+"访问失败",method,sendJson]);
                }
            }
        }
        httpRequest.send(JSON.stringify(sendJson)); //设置为发送给服务器数据
    }

    this.getOs = function () {
        let sUserAgent = navigator.userAgent;
        let isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows");
        let isMac = (navigator.platform == "Mac68K") || (navigator.platform == "MacPPC") || (navigator.platform == "Macintosh") || (navigator.platform == "MacIntel");
        if (isMac) return "Mac";
        let isUnix = (navigator.platform == "X11") && !isWin && !isMac;
        if (isUnix) return "Unix";
        let isLinux = (String(navigator.platform).indexOf("Linux") > -1);
        if (isLinux) return "Linux";
        if (isWin) {
            let isWin2K = sUserAgent.indexOf("Windows NT 5.0") > -1 || sUserAgent.indexOf("Windows 2000") > -1;
            if (isWin2K) return "Win2000";
            let isWinXP = sUserAgent.indexOf("Windows NT 5.1") > -1 || sUserAgent.indexOf("Windows XP") > -1;
            if (isWinXP) return "WinXP";
            let isWin2003 = sUserAgent.indexOf("Windows NT 5.2") > -1 || sUserAgent.indexOf("Windows 2003") > -1;
            if (isWin2003) return "Win2003";
            let isWinVista= sUserAgent.indexOf("Windows NT 6.0") > -1 || sUserAgent.indexOf("Windows Vista") > -1;
            if (isWinVista) return "WinVista";
            let isWin7 = sUserAgent.indexOf("Windows NT 6.1") > -1 || sUserAgent.indexOf("Windows 7") > -1;
            if (isWin7) return "Win7";
            let isWin10 = sUserAgent.indexOf("Windows NT 10") > -1 || sUserAgent.indexOf("Windows 10") > -1;
            if (isWin10) return "Win10";
        }
        return "other";
    }

    this.isNull=function (d){
        if(d==null||typeof d == "undefined"){
            return true;
        }
        return false;
    }

    /**获得浏览器***/
    this.getBrowse = function () {
        let browser = {};
        let userAgent = navigator.userAgent.toLowerCase();
        let s;
        (s = userAgent.match(/msie ([\d.]+)/)) ? browser.ie = s[1] : (s = userAgent.match(/firefox\/([\d.]+)/)) ? browser.firefox = s[1] : (s = userAgent.match(/chrome\/([\d.]+)/)) ? browser.chrome = s[1] : (s = userAgent.match(/opera.([\d.]+)/)) ? browser.opera = s[1] : (s = userAgent.match(/version\/([\d.]+).*safari/)) ? browser.safari = s[1] : 0;
        let version = "";
        if (browser.ie) {
            version = 'IE ' + browser.ie;
        }
        else {
            if (browser.firefox) {
                version = 'firefox ' + browser.firefox;
            }
            else {
                if (browser.chrome) {
                    version = 'chrome ' + browser.chrome;
                }
                else {
                    if (browser.opera) {
                        version = 'opera ' + browser.opera;
                    }
                    else {
                        if (browser.safari) {
                            version = 'safari ' + browser.safari;
                        }
                        else {
                            version = '未知浏览器';
                        }
                    }
                }
            }
        }
        return version;
    }
    this.formatDate = function (date,fmt) {
        if(date==null){
            return null;
        }
        if(this.isNull(fmt))
            fmt="yyyy-MM-dd HH:mm:ss.S";
        let o = {
            "M+" : date.getMonth()+1,                 //月份
            "d+" : date.getDate(),                    //日
            "h+" : date.getHours(),                   //小时
            "H+" : date.getHours(), //小时
            "m+" : date.getMinutes(),                 //分
            "s+" : date.getSeconds(),                 //秒
            "q+" : Math.floor((date.getMonth()+3)/3), //季度
            "S"  : date.getMilliseconds()             //毫秒
        };
        if(/(y+)/.test(fmt)) {
            fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length));
        }
        for(let k in o) {
            if(new RegExp("("+ k +")").test(fmt)){
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
            }
        }
        return fmt;
    }
    this.domChange=function (refreshTime,fun){
        let refresh=false;
        let observer = new MutationObserver(function (mutationRecoards, observer) {
            refresh=true;
        });
        observer.observe(document.documentElement, {
            attributes: true,
            characterData: true,
            childList: true,
            subtree: true
        });
        setInterval(function() {
            if(refresh) {
                refresh=false;
                fun&&fun();
            }
        }, refreshTime);
    }
}


